using System;
using Mono.Linker.Tests.Cases.Expectations.Assertions;
using Mono.Linker.Tests.Cases.Expectations.Metadata;

namespace Mono.Linker.Tests.Cases.UnreachableBlock
{
    [SetupCSharpCompilerToUse("csc")]
    [SetupCompileArgument("/optimize+")]
    [SetupLinkerArgument("--enable-opt", "ipconstprop")]
    class MethodArgumentPropagation
    {
        public static void Main()
        {
            TestSimpleStaticCall();
            TestFailedAndSuccessfullPropagation();
            TestComplexButAlwaysConstant();
            TestModifiesArgumentOnStack();
            TestConditionalStaticCall();
            TestSimpleLocalVariable();
            TestConditionalJumpIntoReplacedTarget(3);
            TestNullPropagation();
            TestFirstLevelReduction();
            TestConditionalArguments();
            TestConditionalArguments_2();

            TestRecursionFromDeadCode();
            TestIndirectRecursion();
            TestStringCalls();
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "nop",
            "ldc.i4 0x0",
            "brfalse.s il_8",
            "ret",
        })]
        static void TestSimpleStaticCall()
        {
            if (StaticBool(4))
                NeverReached();
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "nop",
            "ldc.i4 0x0",
            "brfalse.s il_8",
            "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::GetUnknownValue()",
            "call System.Boolean Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::SimpleCompare(System.Int32)",
            "brfalse.s il_19",
            "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::Reached()",
            "ret",
        })]
        static void TestFailedAndSuccessfullPropagation()
        {
            if (SimpleCompare(GetConstValue()))
                NeverReached();

            if (SimpleCompare(GetUnknownValue()))
                Reached();
        }

        [Kept]
        static bool SimpleCompare(int arg)
        {
            return arg == 3;
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "ldc.i4.1",
            "ldstr 'aa '",
            "call System.String System.String::Trim()",
            "ldc.i4.2",
            "newarr System.Object",
            "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::ComplexButAlwaysConstant(System.Int32,System.String,System.Object[])",
            "ldc.i4.0",
            "ble.s il_19",
            "ret",
        })]
        static void TestComplexButAlwaysConstant()
        {
            if (ComplexButAlwaysConstant(1, "aa ".Trim(), new object[] { null, null }) > 0)
                NeverReached();
        }

        [Kept]
        static int ComplexButAlwaysConstant(int arg, string s, object[] array)
        {
            return -1;
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "ldc.i4.3",
            "stloc.0",
            "ldloca.s",
            "call System.Int32 Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::ModifiesArgumentOnStack(System.Int32&)",
            "ldc.i4.1",
            "beq.s il_11",
            "call System.Void Mono.Linker.Tests.Cases.UnreachableBlock.MethodArgumentPropagation::Reached()",
            "ret",
        })]
        static void TestModifiesArgumentOnStack()
        {
            int value = 3;
            if (ModifiesArgumentOnStack(ref value) != 1)
                Reached();
        }

        [Kept]
        static int ModifiesArgumentOnStack(ref int arg)
        {
            arg = 2;
            return 1;
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "nop",
            "ldc.i4.s 0x9",
            "ldc.i4.1",
            "bne.un.s il_6",
            "ret",
        })]
        static void TestConditionalStaticCall()
        {
            if (ConditionalReturn(false) == 1)
                NeverReached();
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "nop",
            "ldstr 'a'",
            "call System.Void System.Console::WriteLine(System.String)",
            "ret",
        })]
        static void TestSimpleLocalVariable()
        {
            Console.WriteLine(LocalVariableMix(int.MinValue));
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "ldarg.0",
            "ldc.r8 0",
            "bge.un.s il_1c",
            "ldstr 'd'",
            "ldstr 's'",
            "newobj System.Void System.ArgumentOutOfRangeException::.ctor(System.String,System.String)",
            "throw",
            "ldstr 's'",
            "call System.Void System.Console::WriteLine(System.String)",
            "ldarg.0",
            "ldc.r8 0",
            "bge.un.s il_42",
            "ldstr 'd'",
            "ldstr 's'",
            "newobj System.Void System.ArgumentOutOfRangeException::.ctor(System.String,System.String)",
            "throw",
            "ldstr 's'",
            "call System.Void System.Console::WriteLine(System.String)",
            "ret",
        })]
        static void TestConditionalJumpIntoReplacedTarget(double d)
        {
            if (d < 0)
                throw new ArgumentOutOfRangeException(nameof(d), GetString());

            Console.WriteLine(GetString());

            if (d < 0)
                throw new ArgumentOutOfRangeException(nameof(d), GetString());

            Console.WriteLine(GetString());
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "nop",
            "ldnull",
            "brfalse.s il_4",
            "ret",
        })]
        static void TestNullPropagation()
        {
            if (GetNull(2) is not null)
                NeverReached();
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "nop",
            "ldc.i4.4",
            "ldc.i4.4",
            "beq.s il_5",
            "nop",
            "ldc.i4.s 0xa",
            "call System.Void System.Console::WriteLine(System.Int32)",
            "ret",
        })]
        static void TestFirstLevelReduction()
        {
            if (SimpleIntInOut(4) != 4)
                NeverReached();

            Console.WriteLine(SimpleIntInOut(10));
        }

        [Kept]
        static void TestConditionalArguments()
        {
            if (KeptIntInOut(GetUnknownValue() > 0 ? 2 : 3) != 4)
                Reached();
        }

        [Kept]
        static void TestConditionalArguments_2()
        {
            if (KeptIntInOut(GetUnknownValue() > 0 ? 2 : 3, 1) != 4)
                Reached();
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "nop",
            "ldc.i4.0",
            "ldc.i4.1",
            "bne.un.s il_5",
            "ret",
        })]
        static void TestRecursionFromDeadCode()
        {
            if (RecursionFromDeadCode(3) == 1)
            {
                NeverReached();
            }
        }

        static int RecursionFromDeadCode(int arg)
        {
            if (arg > 0)
            {
                if (StaticBool(4))
                {
                    return 1;
                }
            }
            else
            {
                RecursionFromDeadCode(--arg);
            }

            return 0;
        }

        [Kept]
        static int TestIndirectRecursion()
        {
            return TestIndirectRecursion_1();
        }

        [Kept]
        static int TestIndirectRecursion_1()
        {
            return TestIndirectRecursion_2();
        }

        [Kept]
        static int TestIndirectRecursion_2()
        {
            return TestIndirectRecursion();
        }

        [Kept]
        [ExpectedInstructionSequence(new[] {
            "nop",
            "nop",
            "nop",
            "ldc.i4 0x0",
            "brfalse.s il_a",
            "nop",
            "nop",
            "nop",
            "ldc.i4 0x0",
            "brfalse.s il_14",
            "ret",
        })]
        static void TestStringCalls()
        {
            string s = GetStringValue("s");
            if (StringsEqual(s, "v"))
                NeverReached();

            if (StringsNotEqual(GetStringValue("s"), "s"))
                NeverReached();
        }

        static bool StringsEqual(string a, string b)
        {
            return a == b;
        }

        static bool StringsNotEqual(string a, string b)
        {
            return a != b;
        }

        static bool StaticBool(int arg)
        {
            return arg == 3;
        }

        static int ConditionalReturn(bool arg)
        {
            if (arg)
                return 1;

            return 9;
        }

        static string LocalVariableMix(int s)
        {
            int l = int.MinValue;
            return l == s ? "a" : "b";
        }

        static string GetString()
        {
            return "s";
        }

        static object GetNull(int arg)
        {
            return arg > 5 ? 9 : null;
        }

        static int SimpleIntInOut(int arg)
        {
            return arg;
        }

        static int GetConstValue()
        {
            return 5;
        }

        static string GetStringValue(string s)
        {
            return s;
        }

        [Kept]
        static int GetUnknownValue()
        {
            return Environment.ProcessId + 10;
        }

        [Kept]
        static int KeptIntInOut(int arg)
        {
            return arg;
        }

        [Kept]
        static int KeptIntInOut(int arg, int unused)
        {
            return arg;
        }

        [Kept]
        static void Reached()
        {
        }

        static void NeverReached()
        {
        }
    }
}
